﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;

namespace Sabnzbd
{
    public class SABnzbd
    {
        private const string cKeyValueRegex = @"\[(?<Group>[\w]*)\]|\[\[(?<SubGroup>.*)\]\]|(?<Key>.*) = (?<Value>.*[^\s]*)\s";
        private const string cFilterRegex = @"(?<Type>.*), ""{0,1}(?<Mode>.??)""{0,1}, \""{0,1}(?<Script>.*?)""{0,1}, (?<Accept>.), (?<Expresion>[\w*\.]*)(?<Unknown>, "".*?"")*";
        private const string cFilterIDRegex = @"(?<FilterID>[\d]{1,3})";
        /// <summary>
        /// Generate a filename using the sabnzbd standards
        /// 
        /// Meaning             Pattern Result 
        /// Show Name           %sn     Show Name 
        ///                     %s.n    Show.Name 
        ///                     %s_n    Show_Name 
        /// Season Number       %s      1 
        ///                     %0s     01 
        /// Episode Number      %e      5 
        ///                     %0e     05 
        /// Episode Name        %en     Episode Name 
        ///                     %e.n    Episode.Name 
        ///                     %e_n    Episode_Name 
        /// File Extension      %ext    avi
        /// Original filename   %o      original file name
        ///                     ""      Leave blank after trailing slash to get original filename
        /// </summary>
        /// <param name="baseFolder"></param>
        /// <param name="renameInfo"></param>
        /// <param name="showName"></param>
        /// <param name="season"></param>
        /// <param name="episode"></param>
        /// <param name="episodeTitle"></param>
        /// <param name="originalFileName"></param>
        /// <returns></returns>
        public static string CreateSabnzbdFileName(string baseFolder, string renameInfo, string showName, int season, int episode, string episodeTitle, string originalFileName)
        {
            if (renameInfo.Equals(""))
                renameInfo = "%fn";

            string newFileName = renameInfo;

            // replace operators with information supplied
            newFileName = newFileName.Replace(@"%sn", showName);
            newFileName = newFileName.Replace(@"%s.n", showName.Replace(' ', '.'));
            newFileName = newFileName.Replace(@"%s_n", showName.Replace(' ', '_'));
            newFileName = newFileName.Replace(@"%s", Convert.ToString(season));
            newFileName = newFileName.Replace(@"%0s", Prefix0(Convert.ToString(season)));
            newFileName = newFileName.Replace(@"%en", episodeTitle);
            newFileName = newFileName.Replace(@"%e.n", episodeTitle.Replace(' ', '.'));
            newFileName = newFileName.Replace(@"%e_n", episodeTitle.Replace(' ', '_'));
            newFileName = newFileName.Replace(@"%ext", ExtractExt(originalFileName));
            newFileName = newFileName.Replace(@"%e", Convert.ToString(episode));
            newFileName = newFileName.Replace(@"%fn", ExtractFileName(originalFileName));
            newFileName = newFileName.Replace(@"%0e", Prefix0(Convert.ToString(episode)));

            // remove invalid chars
            newFileName = newFileName.Replace(':', '_');
            newFileName = newFileName.Replace('"', '_');
            newFileName = newFileName.Replace('?', '_');
            newFileName = newFileName.Replace('*', '_');
            newFileName = newFileName.Replace('<', '_');
            newFileName = newFileName.Replace('>', '_');
            newFileName = newFileName.Replace('|', '_');

            // combine the basefolder
            newFileName = Path.Combine(baseFolder, newFileName);

            // check if filename is present
            string fileName = Path.GetFileName(newFileName);
            if (fileName.Equals(""))
            {
                newFileName = Path.Combine(newFileName, originalFileName);
            }

            return newFileName;
        }

        private static string ExtractFileName(string originalFileName)
        {
            return Path.GetFileNameWithoutExtension(originalFileName);
        }

        private static string ExtractExt(string originalFileName)
        {
            string ext = Path.GetExtension(originalFileName);

            if (ext.Length > 0)
            {
                if (ext[0].Equals('.'))
                    ext = ext.Substring(1);
            }

            return ext;
        }

        private static string Prefix0(string input)
        {
            if (input.Length == 1)
                return "0" + input;
            else
                return input;
        }

        /// <summary>
        /// Parses the Sabnzbd ini file and returns the settings objects
        /// </summary>
        /// <param name="iniLocation">Absolute location to the ini file</param>
        /// <returns>Graph representing the ini file</returns>
        public static szdConfig ParseINIFile(string iniLocation)
        {
            if (!File.Exists(iniLocation))
                throw new FileNotFoundException("Sabnzbd INI file not found at specified location");

            string _iniFile = ReadLockedFile(iniLocation);

            SabINISettingGroup _groups = new SabINISettingGroup("Root");
            Regex _RegularExpr = new Regex(cKeyValueRegex, RegexOptions.Compiled);

            _RegularExpr.Match(_iniFile);
            Match m = _RegularExpr.Match(_iniFile);
            Stack<Object> _stack = new Stack<object>();
            while (m.Success)
            {
                if (m.Groups["Group"].Success)
                {
                    _stack.Push(new SabINISettingGroup(m.Groups["Group"].Value.Trim()));                    
                }
                else if (m.Groups["SubGroup"].Success)
                {
                    _stack.Push(new SabINISettingSubGroup(m.Groups["SubGroup"].Value.Trim()));                    
                }
                else if (m.Groups["Key"].Success)
                {
                    _stack.Push(new KeyValuePair<string, string>(m.Groups["Key"].Value.Trim(), m.Groups["Value"].Value.Trim()));
                }

                m = m.NextMatch();
            }

            List<KeyValuePair<string, string>> mTempKVPList = new List<KeyValuePair<string, string>>();
            List<SabINISettingSubGroup> mTempSSGList = new List<SabINISettingSubGroup>();
            
            while (_stack.Count > 0)
            {
                Object objFromStack = _stack.Pop();
                if (objFromStack.GetType() == typeof(KeyValuePair<string, string>))
                {
                    mTempKVPList.Add((KeyValuePair<string, string>)objFromStack);
                }
                else if (objFromStack.GetType() == typeof(SabINISettingSubGroup))
                {
                    SabINISettingSubGroup _CurSubGroup = (SabINISettingSubGroup)objFromStack;
                    foreach (KeyValuePair<string, string> kvp in mTempKVPList)
                    {
                        _CurSubGroup.Settings.Add(kvp);
                    }
                    mTempKVPList.Clear();
                    mTempSSGList.Add(_CurSubGroup);
                }
                else if (objFromStack.GetType() == typeof(SabINISettingGroup))
                {
                    SabINISettingGroup _CurGroup = (SabINISettingGroup)objFromStack;
                    foreach (SabINISettingSubGroup ssg in mTempSSGList)
                    {
                        _CurGroup.SubGroups.Add(ssg);
                    }
                    mTempSSGList.Clear();
                    foreach (KeyValuePair<string, string> kvp in mTempKVPList)
                    {
                        _CurGroup.Settings.Add(kvp);
                    }
                    mTempKVPList.Clear();
                    _groups.Groups.Add(_CurGroup);
                }
            }

            foreach (KeyValuePair<string, string> kvp in mTempKVPList)
            {
                _groups.Settings.Add(kvp);
            }
            mTempKVPList.Clear();

            szdConfig _Config = new szdConfig();

            foreach (KeyValuePair<string, string> kvp in _groups.Settings)
            {
                if (kvp.Key.Equals("__version__"))
                {
                    _Config.Version = ExtractInt(kvp.Value);
                    break;
                }
            }

            foreach (SabINISettingGroup group in _groups.Groups)
            {
                if (group.Name.Equals("misc"))
                {
                    #region Misc
                    foreach (KeyValuePair<string, string> kvp in group.Settings)
                    {
                        if (kvp.Key.Equals("rss_rate"))
                        {
                            _Config.RSSRefreshRate = ExtractInt(kvp.Value);
                        }
                        else if (kvp.Key.Equals("complete_dir"))
                        {
                            _Config.CompleteDir = ExtractString(kvp.Value);
                        }
                        else if (kvp.Key.Equals("tv_sort_string"))
                        {
                            _Config.TVSortString = ExtractString(kvp.Value);
                        }
                        else if (kvp.Key.Equals("email_account"))
                        {
                            _Config.Email.AccountName = ExtractString(kvp.Value);
                        }
                        else if (kvp.Key.Equals("email_server"))
                        {
                            _Config.Email.Server = ExtractString(kvp.Value);
                        }
                        else if (kvp.Key.Equals("email_to"))
                        {
                            _Config.Email.To = ExtractString(kvp.Value);
                        }
                        else if (kvp.Key.Equals("email_from"))
                        {
                            _Config.Email.From = ExtractString(kvp.Value);
                        }
                        else if (kvp.Key.Equals("email_pwd"))
                        {
                            _Config.Email.Password = ExtractString(kvp.Value);
                        }
                        else if (kvp.Key.Equals("email_endjob"))
                        {
                            _Config.Email.EmailAtEndOfJob = ExtractBool(kvp.Value);
                        }
                        else if (kvp.Key.Equals("email_full"))
                        {
                            _Config.Email.EmailAtDiskFull = ExtractBool(kvp.Value);
                        }
                        else if (kvp.Key.Equals("enable_tv_sorting"))
                        {
                            _Config.EnableTVSorting = ExtractBool(kvp.Value);
                        }
                    }
                    #endregion
                }
                else if (group.Name.Equals("rss"))
                {
                    #region RSS
                    Regex _FilterRegex = new Regex(cFilterRegex, RegexOptions.Compiled);
                    Regex _FilterIDRegex = new Regex(cFilterIDRegex, RegexOptions.Compiled);

                    foreach (SabINISettingSubGroup subGroup in group.SubGroups)
                    {
                        szdFeed _Feed = new szdFeed();
                        _Feed.Name = subGroup.Name;
                        SortedList<int, szdFilter> _SortedList = new SortedList<int, szdFilter>();
                        for (int i = subGroup.Settings.Count - 1; i >= 0; i--)
                        {
                            KeyValuePair<string, string> kvp = subGroup.Settings[i];

                            if (kvp.Key.Equals("uri"))
                            {
                                _Feed.Uri = ExtractString(kvp.Value);
                            }
                            else if (kvp.Key.Equals("cat"))
                            {
                                _Feed.CategoryName = ExtractString(kvp.Value);
                            }
                            else if (kvp.Key.StartsWith("filter"))
                            {
                                szdFilter _Filter = new szdFilter();

                                _Filter.ID = Convert.ToInt32(_FilterIDRegex.Match(kvp.Key).Groups["FilterID"].Value);

                                string _Value = ExtractString(kvp.Value);
                                Match _filterMatch = _FilterRegex.Match(_Value);

                                if (_filterMatch.Success)
                                {
                                    if (_filterMatch.Groups["Type"].Success)
                                    {
                                        _Filter.Category = _filterMatch.Groups["Type"].Value;
                                    }
                                    if (_filterMatch.Groups["Accept"].Success)
                                    {
                                        switch (_filterMatch.Groups["Accept"].Value)
                                        {
                                            case "A":
                                                    {
                                                        _Filter.FilterType = szdFilterType.Accept;
                                                        break;
                                                    }
                                            case "R":
                                                    {
                                                        _Filter.FilterType = szdFilterType.Reject;
                                                        break;
                                                    }
                                            case "M":
                                                    {
                                                        _Filter.FilterType = szdFilterType.Must;
                                                        break;
                                                    }
                                            default:
                                                break;
                                        }
                                    }
                                    if (_filterMatch.Groups["Expresion"].Success)
                                    {
                                        _Filter.SearchString = _filterMatch.Groups["Expresion"].Value;
                                    } 
                                    if (_filterMatch.Groups["Mode"].Success)
                                    {
                                        _Filter.Mode = _filterMatch.Groups["Mode"].Value;
                                    } 
                                    if (_filterMatch.Groups["Script"].Success)
                                    {
                                        _Filter.Script = _filterMatch.Groups["Script"].Value;
                                    }
                                }
                                _SortedList.Add(_Filter.ID, _Filter);
                            }
                        }
                        foreach (KeyValuePair<int, szdFilter> _FilterKVP in _SortedList)
                        {
                            _Feed.Filters.Add(_FilterKVP.Value);

                        }
                        _Config.Feeds.Add(_Feed);
                    }
                    #endregion
                }
                else if (group.Name.Equals("categories"))
                {
                    #region Category
                    foreach (SabINISettingSubGroup subGroup in group.SubGroups)
                    {
                        szdCategory _Category = new szdCategory();
                        _Category.Name = subGroup.Name;
                        for (int i = subGroup.Settings.Count - 1; i >= 0; i--)
                        {
                            KeyValuePair<string, string> kvp = subGroup.Settings[i];    
                            if (kvp.Key.Equals("dir"))
                            {
                                _Category.Directory = ExtractString(kvp.Value);
                            }
                            else if (kvp.Key.Equals("newzbin"))
                            {
                                foreach (string item in ExtractString(kvp.Value).Split(','))
                                {
                                    if (item.Trim().Contains('.'))
                                    {
                                        _Category.NewsGroups.Add(item.Trim());
                                    }
                                    else
                                    {
                                        _Category.NewzbinCategory = item.Trim();
                                    }
                                } 
                            } 
                        }
                        _Config.Categories.Add(_Category);
                    }
                    #endregion
                }
            }

            return _Config;
        }

        private static string ReadLockedFile(string location)
        {
            string buffer = "";
            string _tempFile = Path.GetTempFileName();
            File.Copy(location, _tempFile, true);
            try
            {
                TextReader _TR = File.OpenText(_tempFile);

                try
                {
                    buffer = _TR.ReadToEnd();
                }
                finally
                {
                    _TR.Close();
                }
            }
            finally
            {
                File.Delete(_tempFile);
            }
            return buffer;
        }

        private static string ExtractString(string Value)
        {
            if (Value.StartsWith("\""))
                return Value.Trim().Substring(1, Value.Length - 2).Trim();
            else
                return Value.Trim();
        }

        private static int ExtractInt(string Value)
        {
            try
            {
                return Convert.ToInt32(ExtractString(Value));
            }
            catch { return 0;}
        }

        private static bool ExtractBool(string Value)
        {
            try
            {
                return Convert.ToInt32(ExtractString(Value)).Equals(1);
            }
            catch { return false; }
        }
    }
}
